/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @Title: ParseShopStyle.java
 * @Package phoenics.apeak.simple.service
 * @Description
 * @author phoenics@126com
 * @date 2016-1-30 16:01:32
 * @version V1.0
 */
package phoenics.apeak.simple.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import phoenics.apeak.crawl.Discard;
import phoenics.apeak.crawl.Extractor;
import phoenics.apeak.crawl.service.DocumentPublisher;
import phoenics.apeak.crawl.util.LinkTools;
import phoenics.apeak.simple.entity.Brand;
import phoenics.apeak.simple.entity.Goods;
import phoenics.apeak.simple.entity.PromotionalDeal;
import phoenics.apeak.simple.entity.TCategory;
import phoenics.apeak.simple.entity.Goods.Color;
import phoenics.apeak.simple.entity.PromotionalDeal.Retailer;
import phoenics.apeak.simple.store.BrandDao;
import phoenics.apeak.simple.store.GoodsDao;
import phoenics.apeak.simple.store.UrlInfoDao;
import phoenics.apeak.simple.entity.LimitBrand;
import phoenics.apeak.simple.store.LimitBrandDao;

// TODO: Auto-generated Javadoc
/**
 * The Class ParseShopStyle.
 *
 * @Description The Class ParseShopStyle.
 */
@Service
public class ParseShopStyle implements Extractor {
	
	/** The discard shop style. */
	@Autowired
	DiscardShopStyle discardShopStyle;
	/** The url info dao. */
	@Autowired
	private UrlInfoDao urlInfoDao;
	
	/** The goods dao. */
	@Autowired
	GoodsDao goodsDao;
	
	/** The brand dao. */
	@Autowired
	BrandDao brandDao;
	
	/** The document publisher. */
	@Autowired
	DocumentPublisher documentPublisher;
	
	@Autowired
	private LimitBrandDao limitBrandDao;
	/** The category. */
	private String category;
	
	/** The source. */
	private final String source = "shopStyle";
	
	/** The last offset. */
	private int lastOffset = -1;
	
	/** The total. */
	private int total = -1;
	private Map<String, String> limitBrands = new HashMap<>();
	//public final static String url_templ = "http://www.shopstyle.com/api/v2/products?cat=%s&device=desktop&includeCategoryHistogram=false&includeProducts=true&limit=%s&locales=all&offset=%s&pid=shopstyle&productScore=LessPopularityEPC&track=false";
	/** The Constant url_templ. */
	public final static String url_templ = "http://www.shopstyle.com/api/v2/products?cat=%s&device=desktop&includeCategoryHistogram=false&includeProducts=true&limit=%s&locales=all&offset=%s&pid=shopstyle&productScore=LessPopularityEPC&track=false";
	
	/** The goods mapper. */
	ObjectMapper goodsMapper = new ObjectMapper();
	
	/** The image set. */
	Map<String, String> imageSet = new HashMap<>();
	
    private String url;

	/**
 * Gets the category.
 *
 * @return the category
 */
	public String getCategory() {
		return category;
	}

	/**
 * Sets the category.
 *
 * @param category
 *            the new category
 */
	public void setCategory(String category) {
		this.category = category;
	}

	/**
 * Gets the source.
 *
 * @return the source
 */
	public String getSource() {
		return source;
	}

	/**
 * Do product.
 *
 * @param product
 *            the product
 * @Description Do product.
 */
	private void doProduct(JsonNode product) {
		imageSet.clear();
		List<Goods.Stock> stockList=parseStock(product);//判断库存，如果没有库存，就没有必要抓取
		if(stockList==null || stockList.size()==0){
			return;
		}
		Goods.Brand gb= parseBrand( product);
		if(gb==null){
			return;
		}
		Goods goods = new Goods();
		goods.setBrand(gb);
		goods.setMyUrl(this.url);
		goods.setRawId(product.findValue("id").asLong() + "");
		goods.setRefCategory(this.getCategory());
		goods.setCategories(parseCategory(product));
		goods.setSource(this.getSource());
		goods.setName(product.findValue("name").asText());
		goods.setCurrency(product.findValue("currency").asText());
		goods.setTotalPrice(product.findValue("price").asText());
		goods.settCategories(parseTCategory(product));
		goods.setTags(new ArrayList<String>());
		goods.setImages(parseImages(product, "image.sizes"));
		goods.setAlternateImages(parseAlternateImages(product));
		goods.setRetailer(parseRetailerName(product));
		goods.setProductPage(parseProductPage(product));
		goods.setDescription(parseDescription(product));
		goods.setUpdateTime(new Date().getTime());
		goods.setBrand(parseBrand(product));
		goods.setPromotionalDeal(parsePromotionalDeal(product));
		goods.setSalePrice(parseSalePrice(product));
		goods.setStock(stockList);
		goods.setInSock(parseInSock(product));
		goods.setColors(parseColors(product));
		goods.setSizes(parseSizes(product));
		goodsDao.save(goods);
		imageSet.keySet().forEach(key -> {
			if(!StringUtils.isEmpty(key)){
				documentPublisher.publish(ParseImage.class, key);
			}
		});
		imageSet.clear();
	}

	/**
 * Parses the promotional deal.
 *
 * @param product
 *            the product
 * @return the promotional deal
 * @Description Parses the promotional deal.
 */
	private PromotionalDeal parsePromotionalDeal(JsonNode product) {
		JsonNode promotionalDeal = product.path("promotionalDeal");
		if (promotionalDeal == null || promotionalDeal.toString().equals("")) {
			return null;
		}
		PromotionalDeal promotionalDeal2 = new PromotionalDeal();
		promotionalDeal2.setTitle(promotionalDeal.findValue("title").asText());
		Retailer retailer = new Retailer();
		JsonNode retailerj = promotionalDeal.path("retailer");
		
		if (retailerj != null) {
			retailer.setHostDomain(retailerj.findValue("hostDomain").asText());
			retailer.setName(retailerj.findValue("name").asText());
		}
		
		promotionalDeal2.setRetailer(retailer);
		promotionalDeal2.setType(promotionalDeal.findValue("type").asText());
		JsonNode endDatej = promotionalDeal.path("endDate");
		if (endDatej != null) {
			promotionalDeal2.setEndDate(endDatej.findValue("timestamp").asText());
		}
		JsonNode startDatej = promotionalDeal.path("startDate");
		if (startDatej != null) {
			promotionalDeal2.setStartDate(startDatej.findValue("timestamp").asText());
		}
		return promotionalDeal2;
	}

	/**
 * Parses the sale price.
 *
 * @param product
 *            the product
 * @return the string
 * @Description Parses the sale price.
 */
	private  String  parseSalePrice(JsonNode product) {
		JsonNode price =product.findValue("price");
		//product.findValue("price").asText()
		JsonNode salePrice = product.path("salePrice");
		if (salePrice == null && price==null) {
			return "0";
		}
		if (salePrice == null || salePrice.asText().equals("")){
			return price.asText();
		}
		return salePrice.asText();
	}

	/**
 * Parses the stock.
 *
 * @param product
 *            the product
 * @return the list
 * @Description Parses the stock.
 */
	private List<Goods.Stock> parseStock(JsonNode product) {
		JsonNode stock = product.path("stock");
		if (stock == null || stock.toString().equals("")) {
			return null;
		}
		List<Goods.Stock> stocksL = new ArrayList<>();
		ArrayNode stocks=(ArrayNode)stock;
		Iterator<JsonNode> iterator = stocks.elements();
		while(iterator.hasNext()){
			JsonNode stockMe=iterator.next();
			Goods.Stock gs=new Goods.Stock();
			 JsonNode color=  LinkTools.xpath(stockMe,"color.name");
			 JsonNode size=  LinkTools.xpath(stockMe,"size.name");
			 gs.setColor(color.asText());
			 gs.setSize(size.asText());
			 stocksL.add(gs);
		}
		return stocksL;
	}

	/**
 * Parses the in sock.
 *
 * @param product
 *            the product
 * @return true, if successful
 * @Description Parses the in sock.
 */
	private boolean parseInSock(JsonNode product) {
		JsonNode inStock = product.path("inStock");
		if (inStock == null || inStock.toString().equals("")) {
			return false;
		}
		return inStock.asBoolean();
	}

	/**
 * Parses the colors.
 *
 * @param product
 *            the product
 * @return the list
 * @Description Parses the colors.
 */
	private List<Color> parseColors(JsonNode product) {
		JsonNode colors = product.path("colors");
		if (colors == null || colors.toString().equals("")) {
			return null;
		}
		List<Goods.Color> colorsL = new ArrayList<>();
		Iterator<JsonNode> iterator = colors.elements();
		while(iterator.hasNext()){
			JsonNode stockMe=iterator.next();
			Goods.Color gs=new Goods.Color();
			List<String> canonicalColors = new ArrayList<>();
			JsonNode canonicalColors_ = stockMe.path("canonicalColors");
			if (canonicalColors_ != null) {
				Iterator<JsonNode> iterator_c = canonicalColors_.elements();
				while(iterator_c.hasNext()){
					canonicalColors.add(iterator_c.next().findValue("name").asText());
				}
				gs.setCanonicalColors(canonicalColors);
			}
			gs.setImage(parseImages(stockMe, "image.sizes"));
			gs.setName(stockMe.findValue("name").asText());
			colorsL.add(gs);
		}
		return colorsL;
	}

	/**
 * Parses the sizes.
 *
 * @param product
 *            the product
 * @return the list
 * @Description Parses the sizes.
 */
	private List<String> parseSizes(JsonNode product) {
		JsonNode sizes = product.path("sizes");
		if (sizes == null || sizes.toString().equals("")) {
			return null;
		}
		List<String> sizesL = new ArrayList<>();
		Iterator<JsonNode> iterator = sizes.elements();
		while(iterator.hasNext()){
			JsonNode me=iterator.next();
			sizesL.add(me.findValue("name").asText());
		}
		return sizesL;
	}

	/**
 * Parses the description.
 *
 * @param product
 *            the product
 * @return the string
 * @Description Parses the description.
 */
	private String parseDescription(JsonNode product) {
		JsonNode description = product.path("description");
		if (description == null || description.toString().equals("")) {
			return "";
		}
		return description.asText();
	}

	

	/**
 * Parses the brand.
 *
 * @param product
 *            the product
 * @return the goods. brand
 * @Description Parses the brand.
 */
	private Goods.Brand parseBrand(JsonNode product) {
		JsonNode brand_p = product.path("brand");
		if (brand_p == null || brand_p.toString().equals("")) {
			return null;
		}
		String name = "";
		Goods.Brand gBrand = new Goods.Brand("", "");
		if (brand_p != null) {
			Brand brand = new Brand();
			JsonNode name1 = brand_p.findValue("name");
			if (name1 != null) {
				name = name1.asText().replaceAll("\\s+", " ");
			}
			String hand = name.toLowerCase().replaceAll(" ", "-");
			if(this.limitBrands.get(hand)==null){
				return null;
			}
			brand.setName(name);
			gBrand.setName(name);
			brand.setHand(hand);
			gBrand.setHand(hand);
			brand.setLogoUrl("");
			JsonNode url = LinkTools.xpath(brand_p, "logo.sizes.default.url");
			if (url != null) {
				brand.setLogoUrl(url.asText());
			}
			if (!brand.getName().equals("")) {
				brandDao.save(brand);
			}

		}
		return gBrand;
	}

	/**
 * Parses the product page.
 *
 * @param product
 *            the product
 * @return the string
 * @Description Parses the product page.
 */
	private String parseProductPage(JsonNode product) {
		// product.clickUrl
		JsonNode clickUrl = product.path("clickUrl");
		if (clickUrl == null || clickUrl.toString().equals("")) {
			return "";
		}
		String url = clickUrl.asText();
		documentPublisher.publish(ParseRetailerProduct.class, url);
		return url;
	}

	/**
 * Parses the retailer name.
 *
 * @param product
 *            the product
 * @return the string
 * @Description Parses the retailer name.
 */
	private String parseRetailerName(JsonNode product) {
		if (product.path("retailer") != null) {
			return product.path("retailer").findValue("name").asText();
		}
		return "";
	}

	/**
 * Parses the alternate images.
 *
 * @param product
 *            the product
 * @return the list
 * @Description Parses the alternate images.
 */
	private List<String> parseAlternateImages(JsonNode product) {
		List<String> images = new ArrayList<>();
		// ObjectNode image = mapper.createObjectNode();
		if (product.path("alternateImages") == null) {
			return images;
		}
		ArrayNode alternateImages = (ArrayNode) product.path("alternateImages");
		for (JsonNode imageNode : alternateImages) {
			images.add(parseImages(imageNode, "sizes").get(0));
		}
		return images;
	}

	/**
 * Parses the images.
 *
 * @param product
 *            the product
 * @param root
 *            the root
 * @return the list
 * @Description Parses the images.
 */
	private List<String> parseImages(JsonNode product, String root) {
		List<String> images = new ArrayList<>();
		String[] ii = { "Large", "Best", "Medium", "IPhone" };
		// String tt="image.sizes";
		String tt = root;
		int test = 0;
		for (String large : ii) {
			JsonNode p =LinkTools.xpath(product, tt + "." + large);
			if (p != null) {
				images.add(p.path("url").asText());
				imageSet.put(p.path("url").asText(), "");
				test++;
				break;
			}
		}
		if (test == 0) {
			JsonNode p = LinkTools.xpath(product, tt);
			if (p != null) {
				String key = p.fields().next().getKey();
				images.add(p.path(key).path("url").asText());
				imageSet.put(p.path(key).path("url").asText(), "");
			}
		}
		return images;
	}

	/**
 * Parses the category.
 *
 * @param product
 *            the product
 * @return the list
 * @Description Parses the category.
 */
	private List<Goods.Category> parseCategory(JsonNode product) {
		List<Goods.Category> lt = new ArrayList<>();
		ArrayNode categories = (ArrayNode) product.path("categories");
		
		for (JsonNode category : categories) {
			String id = ((ObjectNode) category).findValue("id").asText();
			String name = ((ObjectNode) category).findValue("name").asText();
			Goods.Category t = new Goods.Category();
			t.setId(id);
			t.setName(name);
			lt.add(t);
		}
		return lt;
	}

	/**
 * Parses the t category.
 *
 * @param product
 *            the product
 * @return the list
 * @Description Parses the t category.
 */
	private List<TCategory> parseTCategory(JsonNode product) {
		List<TCategory> lt = new ArrayList<>();
		ArrayNode categories = (ArrayNode) product.path("categories");
		for (JsonNode category : categories) {
			String id = ((ObjectNode) category).findValue("id").asText();
			String name = ((ObjectNode) category).findValue("name").asText();
			TCategory t = new TCategory();
			t.setId(id);
			t.setName(name);
			lt.add(t);
		}
		return lt;
	}

	/**
 * Parses the json.
 *
 * @param content
 *            the content
 * @Description Parses the json.
 */
	private void parseJson(String content) {
		List<LimitBrand> limitBrand=limitBrandDao.findAll();
		limitBrand.forEach(l->{
			limitBrands.put(l.getHand(), "");
		});
		try {
			JsonNode rootNode = goodsMapper.readTree(content);
			JsonNode products = rootNode.path("products");
			ObjectNode metadata = (ObjectNode) rootNode.path("metadata");
			int this_offset = metadata.findValue("offset").asInt();
			int this_limit = metadata.findValue("limit").asInt();
			int total = metadata.findValue("total").asInt();
			int nextOffset = this_offset + this_limit;
			if (this.total < 0) {
				this.total = total;
			}
			if (this.total > total) {
				this.total = total;
			}

			Iterator<JsonNode> iterator = products.elements();
			while (iterator.hasNext()) {
				doProduct(iterator.next());
			}
			updateUrlInfo(nextOffset,this.total);
			this.setLastOffset(this_offset);
		} catch (Exception e) {
			System.err.println(e.getMessage());
		}
	}
	

	/**
 * Update url info.
 *
 * @param nextOffset
 *            the next offset
 * @param total
 *            the total
 * @Description Update url info.
 */
	private void updateUrlInfo(int nextOffset,int total) {
		Query query = new Query();
		query.addCriteria(Criteria.where("id").is(this.getCategory()));
		Update update = new Update();
		update.set("total", total);
		update.set("offset", nextOffset);
		if (nextOffset >= total) {
			update.set("over", 1);
		}
		urlInfoDao.updateFirst(query, update);
	}

	/**
 * Gets the Constant url_templ.
 *
 * @return the Constant url_templ
 */
	public String getUrl_templ() {
		return url_templ;
	}

	/* (non-Javadoc)
	 * @see phoenics.apeak.crawl.Extractor#setParams(java.lang.String)
	 */
	@Override
	public void setParams(String params) {
		/*
		 * params={category:"",offset:0,total:-1}
		 */
		if (params == null) {
			return;
		}
		try {
			JsonNode rootNode = goodsMapper.readTree(params);
			ObjectNode metadata = (ObjectNode) rootNode;
			this.category = metadata.get("category").asText();
			this.setLastOffset(metadata.get("offset").asInt());
			this.total = metadata.get("total").asInt();
			// this.lastOffset=metadata.get("lastOffset").asInt();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/* (non-Javadoc)
	 * @see phoenics.apeak.crawl.Extractor#setUrl(java.lang.String)
	 */
	@Override
	public void setUrl(String url) {
		this.url = url;
	}

	/* (non-Javadoc)
	 * @see phoenics.apeak.crawl.Extractor#extract(byte[])
	 */
	@Override
	public void extract(byte[] content) {
		parseJson(new String(content));
	}

	/* (non-Javadoc)
	 * @see phoenics.apeak.crawl.Bootstrap#start(java.lang.String)
	 */
	

	/**
 * Gets the last offset.
 *
 * @return the last offset
 */
	public int getLastOffset() {
		return lastOffset;
	}

	/**
 * Sets the last offset.
 *
 * @param lastOffset
 *            the new last offset
 */
	public void setLastOffset(int lastOffset) {
		this.lastOffset = lastOffset;
	}
	
	/* (non-Javadoc)
	 * @see phoenics.apeak.crawl.Extractor#getDiscard()
	 */
	@Override
	public Discard getDiscard(){
		return this.discardShopStyle;
		
	}

}
